package org.goldenport.g3.messages

import com.asamioffice.goldenport.util.OptionUtil.lift
import com.asamioffice.goldenport.text.StringUtil.makeShortString
import org.goldenport.g3._
import org.apache.commons.fileupload.FileItem
import com.asamioffice.goldenport.text.UPathString
import org.goldenport.wpath.WPath
import com.asamioffice.goldenport.text.UString
import scala.util.matching.Regex
import scala.util.matching.Regex.Match
import scalaz._
import Scalaz._

/**
 * @since   Apr. 12, 2010
 * @version Nov. 13, 2011
 * @version Dec.  5, 2011
 * @author  ASAMI, Tomoharu
 */
object Get {
  def apply() = new Get("", None)

  def apply(uri: String) = new Get(uri, None)

  def apply(uri: String, content: AnyRef) = new Get(uri, lift(content))

  def apply(uri: MsgVar) = new VGet(uri, None)

  def apply(uri: MsgVar, content: AnyRef) = new VGet(uri, lift(content))
}

object Put {
  def apply(uri: String = "", content: AnyRef = null) = new Put(uri, lift(content))
}

object Post {
  def apply() = new Post("", None)

  def apply(uri: String) = new Post(uri, None)

  def apply(uri: String, content: AnyRef) = new Post(uri, lift(content))

  def apply(uri: Symbol, content: AnyRef) = new Post(uri.name, lift(content))

  def apply(uri: MsgVar) = new VPost(uri, None)

  def apply(uri: MsgVar, content: AnyRef) = new VPost(uri, lift(content))

  def apply(uri: String, source: RestCommand) = new Post(uri, source.content, Some(source))
}

object Delete {
  def apply(uri: String = "", content: AnyRef = null) = new Delete(uri, lift(content))
}

object Mkcol {
  def apply(uri: String = "") = new Mkcol(uri)
}

object Copy {
  def apply(fromUri: String, toUri: String) = new Copy(fromUri, toUri)
}

object Move {
  def apply(fromUri: String, toUri: String) = new Move(fromUri, toUri)
}

abstract class RestCommand(
  uri: String,
  val content: Option[AnyRef] = None,
  uri2: Option[String] = None,
  val source: Option[RestCommand]
) extends G3Command(List(Some(uri), uri2).flatten) {
  require (uri != null)

  def consume(uri: String): RestCommand

/*
  def getAttachments(): List[Attachment] = source match {
    case Some(c) => c.getAttachments()
    case None => Nil
  }
*/

  def getFileAttachments(): List[FileAttachment] = {
    source.cata(_.getFileAttachments, Nil)
  }

  def getFieldAttachments(): List[FieldAttachment] = {
    source.cata(_.getFieldAttachments, Nil)
  }

  def asRecord: Record = {
    content match {
      case Some(r: Record) => r
      case Some(s: String) => Record(s)
      case _ => Record()
    }
  }

  def asRecords: RecordSet = {
    content match {
      case Some(rs: RecordSet) => rs
      case Some(r: Record) => RecordSet(r)
      case Some(s: String) => RecordSet(Record(s))
      case _ => RecordSet()
    }
  }

  private def _whole_match[A](r: Regex, s: String, a: => A, b: => A): A = {
    import UString.isNull
    r.findFirstMatchIn(s) match {
      case Some(m) if (isNull(m.after) && isNull(m.before)) => a
      case None => b
    }
  }

  def asOperation: (String, List[AnyRef], Map[String, AnyRef]) = {
    def wholematch[A](s: String, a: => A, b: => A): A = {
      _whole_match("""_\d+""".r, s, a, b)
    }
    def mergedivide(
        p: Map[String, AnyRef],
        f: List[FieldAttachment],
        a: List[FileAttachment]): (List[AnyRef], Map[String, AnyRef]) = {
      val zero = Pair(List.empty[AnyRef], Map.empty[String, AnyRef])
      val (parg, pprop) = (zero /: p) { case ((s1, s2), (k, v)) =>
        wholematch(k, (s1 :+ v, s2), (s1, s2 + Pair(k, v))) 
      }
      val (farg, fprop) = (zero /: f) { case ((s1, s2), x) =>
        val v = x.value
        wholematch(x.fieldName, (s1 :+ v, s2), (s1, s2 + Pair(x.fieldName, v)))
      }
      val (aarg, aprop) = (zero /: a) { case ((s1, s2), x) =>
        x
        wholematch(x.fieldName, (s1 :+ x, s2), (s1, s2 + Pair(x.fieldName, x)))
      }
      (parg ::: farg ::: aarg, pprop ++ fprop ++ aprop)
    }

    val path = WPath(uri)
    val op = path.exprs(0).name
    val afiles = getFileAttachments()
    val afields = getFieldAttachments() 
    val record = asRecord
    val params: Map[String, AnyRef] = if (record.isEmpty) {
      path.exprs.last.query match {
        case Some(q) => q.queries
        case None => Map.empty
      }
    } else {
      record.toMap
    }
    val (a, p) = mergedivide(params, afields, afiles)
    (op, a, p)
  }

  def asOperation1: (String, List[AnyRef], Map[String, AnyRef]) = {
    val path = WPath(uri)
    val op = path.exprs(0).name
    val args = path.exprs.tail.map(_.name) ::: getFileAttachments()
    val record = asRecord
    val params: Map[String, AnyRef] = if (record.isEmpty) {
      path.exprs.last.query match {
        case Some(q) => q.queries
        case None => Map.empty
      }
    } else {
      record.toMap
    }
    (op, args, params)
  }

/*
  def action(p: Post): AnyRef = {
    try {
      val files = p.getAttachments()
      val file = files(0)
      val sm = new SimpleModeler(Array())
      sm.useFirstLeafExporter()
      val result = sm.generateDiagram(file.name, file.get())
      result match {
        case Some(r) => r match {
          case b: BinaryContent => Getted(file.name, b)
          case _ => "no result"
        }
        case None => "no result"
      }
    } catch {
      case e => _exception(e);throw e
    }
  }*/

  def asText: String = {
    content match {
      case Some(s: G3TextMimeTyped) => s.toText
      case Some(s: String) => s
//      case Some(s: { def toText: String }) => s.toText
      case Some(s) => s.toString
      case None => ""
    }
  }

  def asString: String = {
    content match {
      case Some(s: String) => s
      case Some(s) => s.toString
      case None => ""
    }
  }

  override def toString() = {
    "%s(%s, %s)".format(getClass.getSimpleName, uri, makeShortString(content))
  }

  protected final def normalize_uri(pattern: String) = {
    val regex = pattern.r
    val consumed = uri.substring(regex.findFirstIn(uri).get.length)
    // remove suffix (e.g. .json, .atom)
    val si = consumed.lastIndexOf("/")
    val di = consumed.lastIndexOf(".")
    if (di == -1) consumed
    else if (si == -1) consumed.substring(0, di)
    else if (si < di) consumed.substring(0, di)
    else consumed
  }
}

trait Attachment {
  def fieldName: String
}

class FileAttachment(val fileItem: FileItem) extends Attachment {
  def fieldName = fileItem.getFieldName()
  def name = fileItem.getName()
  def get() = fileItem.get()
}

class FieldAttachment(val fileItem: FileItem) extends Attachment {
  def fieldName = fileItem.getFieldName()
  def value: String = fileItem.getString()
}

/**
 * Http GET
 * WebDAV GET
 * safe, idempotent
 */
class Get(
  uri: String,
  content: Option[AnyRef] = None,
  source: Option[RestCommand] = None
) extends RestCommand(uri, content, source = source) {
  def copy(uri: String = uri,
           content: Option[AnyRef] = content,
           source: Option[RestCommand] = source) = {
    new Get(uri, content, source)
  }

  def consume(pattern: String) = {
    copy(normalize_uri(pattern), content, Some(this))
  }
}

class VGet(val uri: MsgVar, val content: Option[AnyRef]) extends G3Message {
  override def normalize(ctx: G3AgentContext) = {
//    println("VGet")
    List(new Get(uri.toString(ctx), content))
  }
}

/**
 * Http PUT
 * WebDAV PUT
 * idempotent
 */
class Put(
  uri: String,
  content: Option[AnyRef],
  source: Option[RestCommand] = None
) extends RestCommand(uri, content, source = source) {
  def copy(uri: String = uri,
           content: Option[AnyRef] = content,
           source: Option[RestCommand] = source) = {
    new Put(uri, content, source)
  }

  def consume(pattern: String) = {
    copy(normalize_uri(pattern), content, Some(this))
  }
  
}

class VPut(val uri: MsgVar, val content: Option[AnyRef]) extends G3Message {
  override def normalize(ctx: G3AgentContext) = {
    List(new Put(uri.toString(ctx), content))
  }
}

/**
 * Http POST
 * WebDAV POST
 */
class Post(
  uri: String,
  content: Option[AnyRef],
  source: Option[RestCommand] = None
) extends RestCommand(uri, content, source = source) {
  def copy(uri: String = uri,
           content: Option[AnyRef] = content,
           source: Option[RestCommand] = source) = {
    new Post(uri, content, source)
  }

  def consume(pattern: String) = {
    copy(normalize_uri(pattern), content, Some(this))
  }

  private def apply_schema(content: Option[AnyRef], schema: RecordSchema, context: G3AgentContext) = {
    val ctx = new G3AgentContextRecordContext(context)
    content.map {
      case r: Record => schema.validate(r, ctx)
      case rs: RecordSet => new RecordSchemaRecordSet(rs, schema, ctx)
      case r => r
    }
  }

  def transform(kind: Symbol, schema: RecordSchema, context: G3AgentContext) = {
    new Post(kind.name, apply_schema(content, schema, context))
  }
}

class VPost(val uri: MsgVar, val content: Option[AnyRef]) extends G3Message {
  override def normalize(ctx: G3AgentContext) = {
    List(new Post(uri.toString(ctx), content))
  }
}

/**
 * Http DELETE
 * WebDAV DELETE
 * idempotent
 */
class Delete(
  uri: String,
  content: Option[AnyRef],
  source: Option[RestCommand] = None
) extends RestCommand(uri, content, source = source) {
  def copy(uri: String = uri,
           content: Option[AnyRef] = content,
           source: Option[RestCommand] = source) = {
    new Delete(uri, content, source)
  }

  def consume(pattern: String) = {
    copy(normalize_uri(pattern), content, Some(this))
  }
}

/**
 * WebDAV MKCOL
 */
class Mkcol(
  uri: String,
  source: Option[RestCommand] = None
) extends RestCommand(uri, source = source) {
  def copy(uri: String = uri,
           content: Option[AnyRef] = content,
           source: Option[RestCommand] = source) = {
    new Get(uri, content, source)
  }

  def consume(pattern: String) = {
    copy(normalize_uri(pattern), content, Some(this))
  }
}

/**
 * WebDAV COPY
 */
class Copy(
  val fromUri: String,
  val toUri: String,
  source: Option[RestCommand] = None
) extends RestCommand(fromUri, uri2 = Some(toUri), source = source) {
  def copy(uri: String = uri,
           content: Option[AnyRef] = content,
           source: Option[RestCommand] = source) = {
    new Get(uri, content, source)
  }

  def consume(pattern: String) = {
    copy(normalize_uri(pattern), content, Some(this))
  }
}

/**
 * WebDAV MOVE
 */
class Move(
  val fromUri: String,
  val toUri: String,
  source: Option[RestCommand] = None
) extends RestCommand(fromUri, uri2 = Some(toUri), source = source) {
  def copy(uri: String = uri,
           content: Option[AnyRef] = content,
           source: Option[RestCommand] = source) = {
    new Get(uri, content, source)
  }

  def consume(pattern: String) = {
    copy(normalize_uri(pattern), content, Some(this))
  }
}
